package org.ytor.core.util.scanner;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * created by yangtong on 2025/8/12 20:24:51
 * <br/>
 * 扫描工具，可以不触发类的加载即可扫描到类信息
 * 扫描路径支持的通配符（Ant 风格），基于 Spring 的 PathMatchingResourcePatternResolver:
 * 1. "*": 匹配路径中的任意字符（不跨路径分隔符），相当于“单层通配”
 * 2. "**": 匹配路径中的任意字符（可跨路径分隔符），相当于“多层通配”
 * 3. "?": 匹配路径中的任意单个字符（不跨路径分隔符）
 * 4. {}: 匹配大括号内的任意模式（逗号分隔多个候选），相当于 OR
 * 5. 普通路径写法（无通配符）必须完全匹配
 */

public class ClasspathScanner {

    private static final PathMatchingResourcePatternResolver RESOLVER =
            new PathMatchingResourcePatternResolver();
    private static final CachingMetadataReaderFactory METADATA_FACTORY =
            new CachingMetadataReaderFactory(RESOLVER);

    /**
     * 默认支持的扩展名
     */
    private static final String[] DEFAULT_EXTS = {
            ".class", ".xml", ".yaml", ".yml", ".properties", ".property"
    };

    /**
     * 根据路径通配符(org.abc.*.efg.mapper.*)和断言，返回类路径下符合条件的文件
     */
    public static List<Scanned> scan(String dottedPattern,
                                     Predicate<Scanned> filter) {
        return scan(dottedPattern, Arrays.asList(DEFAULT_EXTS), filter);
    }

    /** 自定义扩展名扫描 */
    public static List<Scanned> scan(String dottedPattern,
                                     Collection<String> exts,
                                     Predicate<Scanned> filter) {
        String antBase = dottedToAntPath(dottedPattern);

        List<Scanned> all = new ArrayList<>();
        for (String ext : exts) {
            String pattern = "classpath*:" + antBase + ext;
            try {
                Resource[] rs = RESOLVER.getResources(pattern);
                for (Resource r : rs) {
                    all.add(buildScanned(r));
                }
            } catch (IOException ignored) {
            }
        }
        if (filter != null) {
            return all.stream().filter(filter).collect(Collectors.toList());
        }
        return all;
    }

    /**
     * 点分->Ant路径，保留 *、** 语义
     */
    private static String dottedToAntPath(String dotted) {
        String s = dotted.trim();
        while (s.startsWith(".")) s = s.substring(1);
        while (s.endsWith(".")) s = s.substring(0, s.length() - 1);
        // 保留 **，其余点替换为 /
        s = s.replace(".", "/");
        return s;
    }

    private static Scanned buildScanned(Resource r) {
        String filename = r.getFilename();
        Type type = Type.OTHER;
        String ext = null;
        if (filename != null) {
            String lower = filename.toLowerCase(Locale.ROOT);
            if (lower.endsWith(".class")) {
                type = Type.CLASS;
                ext = ".class";
            } else if (lower.endsWith(".xml")) {
                type = Type.XML;
                ext = ".xml";
            } else if (lower.endsWith(".yaml")) {
                type = Type.YAML;
                ext = ".yaml";
            } else if (lower.endsWith(".yml")) {
                type = Type.YML;
                ext = ".yml";
            } else if (lower.endsWith(".properties")) {
                type = Type.PROPERTIES;
                ext = ".properties";
            } else if (lower.endsWith(".property")) {
                type = Type.PROPERTIES;
                ext = ".property";
            }
        }
        String className = null;
        if (type == Type.CLASS) {
            try {
                // 直接用 MetadataReader 拿类名最准确
                MetadataReader mr = METADATA_FACTORY.getMetadataReader(r);
                className = mr.getClassMetadata().getClassName();
            } catch (IOException ignored) { /* 退化到路径推断也可 */ }
        }
        return new Scanned(r, type, ext, className);
    }

    // =============== 断言构造器（可自由组合 and()/or()/negate()） =================

    /**
     * 判断是否属于指定的文件类型
     */
    public static Predicate<Scanned> is(Type... types) {
        Set<Type> set = EnumSet.copyOf(Arrays.asList(types));
        return s -> set.contains(s.type());
    }

    /**
     * 文件名精确等于
     */
    public static Predicate<Scanned> fileNameEquals(String name) {
        return s -> {
            String fn = safeName(s);
            return fn != null && fn.equals(name);
        };
    }

    /**
     * 文件名后缀匹配
     */
    public static Predicate<Scanned> fileNameEndsWith(String suffix) {
        String suf = suffix.toLowerCase(Locale.ROOT);
        return s -> {
            String fn = safeName(s);
            return fn != null && fn.toLowerCase(Locale.ROOT).endsWith(suf);
        };
    }

    /**
     * 文件名正则匹配
     */
    public static Predicate<Scanned> fileNameMatchesRegex(String regex) {
        Pattern p = Pattern.compile(regex);
        return s -> {
            String fn = safeName(s);
            return fn != null && p.matcher(fn).matches();
        };
    }

    /**
     * 完整路径（URL 形式）包含某段字符串
     */
    public static Predicate<Scanned> pathContains(String token) {
        String t = token.toLowerCase(Locale.ROOT);
        return s -> {
            try {
                return s.resource().getURI().toString().toLowerCase(Locale.ROOT).contains(t);
            } catch (Exception e) {
                return false;
            }
        };
    }

    /**
     * 判断当前扫描到的类是否可以赋值个指定类型
     */
    public static Predicate<Scanned> isAssignableTo(String targetFqn) {
        return s -> {
            if (s.type() != Type.CLASS) return false;
            try {
                MetadataReader mr = METADATA_FACTORY.getMetadataReader(s.resource());
                return isAssignableTo0(mr, targetFqn);
            } catch (IOException e) {
                return false;
            }
        };
    }

    /**
     * 判断当前扫描到的类是否可以赋值个指定类型
     */
    public static Predicate<Scanned> isAssignableTo(Class<?> targetType) {
        if (targetType == null) throw new IllegalArgumentException("targetType cannot be null");
        return isAssignableTo(targetType.getName());
    }

    /**
     * 类：是否直接/间接标注某注解（包含元注解）
     */
    public static Predicate<Scanned> hasAnnotation(String annotationFqn) {
        return s -> {
            if (s.type() != Type.CLASS) return false;
            try {
                MetadataReader mr = METADATA_FACTORY.getMetadataReader(s.resource());
                AnnotationMetadata am = mr.getAnnotationMetadata();
                return am.hasAnnotation(annotationFqn) || am.hasMetaAnnotation(annotationFqn);
            } catch (IOException e) {
                return false;
            }
        };
    }

    /**
     * 类名（FQN）正则
     */
    public static Predicate<Scanned> classNameMatchesRegex(String regex) {
        Pattern p = Pattern.compile(regex);
        return s -> s.type() == Type.CLASS && s.className() != null && p.matcher(s.className()).matches();
    }

    /**
     * 包名以某前缀开头
     */
    public static Predicate<Scanned> packageStartsWith(String pkgPrefix) {
        String pre = pkgPrefix.endsWith(".") ? pkgPrefix : pkgPrefix + ".";
        return s -> s.type() == Type.CLASS && s.className() != null && s.className().startsWith(pre);
    }

    // --------------------- 内部：类型判断辅助 ---------------------

    private static boolean isAssignableTo0(MetadataReader mr, String targetFqn) throws IOException {
        ClassMetadata cm = mr.getClassMetadata();
        if (cm.getClassName().equals(targetFqn)) return true;

        for (String itf : cm.getInterfaceNames()) {
            if (itf.equals(targetFqn)) return true;
            MetadataReader imr = safeRead(itf);
            if (imr != null && isAssignableTo0(imr, targetFqn)) return true;
        }
        String superName = cm.getSuperClassName();
        if (StringUtils.hasText(superName) && !superName.equals("java.lang.Object")) {
            MetadataReader smr = safeRead(superName);
            if (smr != null) return isAssignableTo0(smr, targetFqn);
        }
        return false;
    }

    private static MetadataReader safeRead(String className) {
        try {
            return METADATA_FACTORY.getMetadataReader(className);
        } catch (IOException e) {
            return null;
        }
    }

    private static String safeName(Scanned s) {
        try {
            return s.resource().getFilename();
        } catch (Exception e) {
            return null;
        }
    }
}

